package com.supreme.cacheserver.aspect;

import com.supreme.cacheserver.annotation.DoubleCache;
import com.supreme.cacheserver.config.condition.DoubleCacheCondition;
import com.supreme.cacheserver.config.props.CacheServerProps;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.annotation.Conditional;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

@Aspect
@Component
@Conditional(value = DoubleCacheCondition.class)
public class DoubleCacheAspect {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Resource
    private CacheServerProps cacheServerProps;

    @Pointcut(value = "@annotation(com.supreme.cacheserver.annotation.DoubleCache)")
    public void PointCut() {
    }

    @Around("PointCut()")
    public Object aroundFilter(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
        Method method = methodSignature.getMethod();

        DoubleCache doubleCacheAnno = AnnotationUtils.findAnnotation(method, DoubleCache.class);
        if (Optional.ofNullable(doubleCacheAnno).map(DoubleCache::cache).isPresent()) {
            String keySeq = doubleCacheAnno.keySeq();

            CacheEvict cacheEvictAnno = AnnotationUtils.findAnnotation(method, CacheEvict.class);
            if (Objects.nonNull(cacheEvictAnno)) {
                String[] values = cacheEvictAnno.value();
                String cacheKey = StringUtils.join(values, keySeq);

                String itemKey = cacheEvictAnno.key();
                if (StringUtils.isNotBlank(itemKey)) {
                    Object[] args = proceedingJoinPoint.getArgs();
                    String suffix = String.valueOf(args[0]);
                    cacheKey = cacheKey + keySeq + suffix;
                    redisTemplate.delete(cacheKey);
                } else {
                    String suffix = doubleCacheAnno.suffix();
                    cacheKey = cacheKey + keySeq + suffix;
                    redisTemplate.delete(cacheKey);
                }

                return proceedingJoinPoint.proceed();
            }

            Cacheable cacheableAnno = AnnotationUtils.findAnnotation(method, Cacheable.class);
            if (Objects.nonNull(cacheableAnno)) {
                Object proceed = proceedingJoinPoint.proceed();

                String[] values = cacheableAnno.value();
                String cacheKey = StringUtils.join(values, keySeq);

                String itemKey = cacheableAnno.key();
                if (StringUtils.isNotBlank(itemKey)) {
                    Object[] args = proceedingJoinPoint.getArgs();
                    String suffix = String.valueOf(args[0]);
                    cacheKey = cacheKey + keySeq + suffix;
                    Object cacheObject = redisTemplate.opsForValue().get(cacheKey);
                    if (Objects.isNull(cacheObject)) {
                        redisTemplate.opsForValue().set(cacheKey, proceed, cacheServerProps.getCacheSeconds(), TimeUnit.SECONDS);
                        return proceed;
                    } else {
                        return cacheObject;
                    }
                } else {
                    String suffix = doubleCacheAnno.suffix();
                    cacheKey = cacheKey + keySeq + suffix;
                    Object cacheObject = redisTemplate.opsForValue().get(cacheKey);
                    if (Objects.isNull(cacheObject)) {
                        redisTemplate.opsForValue().set(cacheKey, proceed, cacheServerProps.getCacheSeconds(), TimeUnit.SECONDS);
                        return proceed;
                    } else {
                        return cacheObject;
                    }
                }
            }
        }

        return proceedingJoinPoint.proceed();
    }
}
